package com.hmdp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdGenerate;
import com.hmdp.utils.ThreadLocalUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.stream.*;
import org.springframework.data.redis.core.StreamOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
@Slf4j
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
    @Resource
    private ISeckillVoucherService seckillVoucherService;
    @Resource
    private RedisIdGenerate redisIdGenerate;

    private final static String VOUCHER_ORDER_KEY_PREFIX = "voucher_order";
    private final static Integer VOUCHER_ORDER_STATUS_NOT_PAY = 1;//订单状态,1:未支付

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedissonClient redissonClient;

    private static final DefaultRedisScript<Long> DESKILL_VOUCHER_SCRIPT;

    //此类AOP代理对象
    private IVoucherOrderService proxy;

    /*静态代码块用来给静态变量初始化赋值*/
    //实现静态变量的初始化
    static {
        DESKILL_VOUCHER_SCRIPT = new DefaultRedisScript<>();
        DESKILL_VOUCHER_SCRIPT.setScriptSource(new ResourceScriptSource(new ClassPathResource("seckillvoucher/seckillVoucherZIGEcondition.lua")));//读取类路径下seckillvoucher/seckillVoucherZIGEcondition.lua脚本文件
        DESKILL_VOUCHER_SCRIPT.setResultType(Long.class); // 设置脚本返回值的类型
    }


    private static final String SEC_KILL_VOUCHER_ORDER_KEY = "sec_kill_voucher_order";
    //阻塞队列
    private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024 * 1024);
    //异步线程池,单线程的线程池
    //private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();
    private static final ExecutorService SECKILL_ORDER_EXECUTOR = new ThreadPoolExecutor(
            1,          // 核心线程数
            1,          // 最大线程数
            0L,         // 线程空闲时间
            TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(1024),  // 任务队列
            Executors.defaultThreadFactory(), // 线程工厂
            new ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝策略
    );

    @PostConstruct//初始化方法,在这个类的bean实例化之后执行(依赖注入完成之后)
    public void init() {
        //1.提交把订单信息从阻塞队列中取出来,并保存到数据库中的异步任务
        SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());//提交任务到线程池中,启动异步单线程任务处理订单信息
        //2.创建消费者组,监听stream.orders这个消息队列
        //3.判断stream.orders消息队列是否存在,不存在则创建该消息队列
        if (Boolean.TRUE.equals(stringRedisTemplate.hasKey("stream.orders"))) {
            log.warn("stream.orders消息队列已存在");
            log.warn("stream.orders消息队列已存在");
            log.warn("stream.orders消息队列已存在");
        }
        /*对应redis命令为:--------XGROUP CREATE stream.orders g1 0 MKSTREAM8----------*/
        /*如果 Stream 不存在，createGroup 方法会自动创建 Stream*/
        // 获取 Stream 操作对象
        StreamOperations<String, Object, Object> streamOps = stringRedisTemplate.opsForStream();
        // Stream 名称
        String streamKey = "stream.orders";
        // 消费者组名称
        String groupName = "g1";
        // 从 Stream 的开始位置读取
        ReadOffset readOffset = ReadOffset.from("0");
        // 创建消费者组
        try {
            String streamOpsGroup = streamOps.createGroup(streamKey, readOffset, groupName);
            System.out.println("Consumer group created successfully.");
        } catch (Exception e) {
            log.error("创建消费者组失败:{},可能是已经存在该消费者组", e.getMessage());
        }
    }

    /**
     * 异步任务处理订单信息:把秒杀优惠卷订单信息异步写入数据库<br>
     * 1.从阻塞队列中取出订单信息<br>
     * 2.保存订单信息到数据库<br>
     */
/*    private class VoucherOrderHandler implements Runnable {
        @Override
        public void run() {
            //使用死循环,一直从阻塞队列中取出订单信息,并保存到数据库中,当队列为空,会阻塞该线程,不会浪费CPU资源
            while (true) {
                try {
                    //1.取出订单信息
                    VoucherOrder voucherOrder = orderTasks.take();
                    //2.保存订单信息到数据库中
                    handleVoucherOrder(voucherOrder);
                } catch (InterruptedException e) {
                    log.error("订单处理异常!!!");
                    throw new RuntimeException(e);
                }
            }
        }
    }*/
    private class VoucherOrderHandler implements Runnable {
        // 提取常量
        private static final String STREAM_KEY = "stream.orders";// 消息队列名称
        private static final String CONSUMER_GROUP = "g1";// 消费者组名称
        private static final String CONSUMER_NAME = "consumer1";// 消费者名称
        private static final int READ_COUNT = 1;// 消息一次读取数量
        private static final long BLOCK_TIME_MS = 2000;// 阻塞时间

        @Override
        public void run() {
            while (true) {
                try {
                    // 1. 获取redis的stream.orders消息队列中的订单信息 XREADGROUP GROUP g1 consumer1 COUNT 1 BLOCK 2000 STREAMS stream.orders >
                    List<MapRecord<String, Object, Object>> read = stringRedisTemplate.opsForStream().read(
                            Consumer.from(CONSUMER_GROUP, CONSUMER_NAME), // 指定消费者组和消费者
                            StreamReadOptions.empty().count(READ_COUNT).block(Duration.ofMillis(BLOCK_TIME_MS)), // 设置读取数量为1, 设置阻塞时间为2000ms
                            StreamOffset.create(STREAM_KEY, ReadOffset.lastConsumed()) // 设置读取位置为lastConsumed(">")
                    );

                    // 2. 判断订单信息是否为空
                    if (read == null || read.isEmpty()) {
                        // 消息获取失败, 继续循环获取, 直到获取到订单信息
                        log.debug("消息获取失败, 继续循环获取,如果一直循环获取失败,则说明消息队列中没有订单信息");
                        continue;
                    }

                    // 3. 订单消息获取成功, 取出订单信息, 保存订单信息到数据库中
                    // 3.1 解析从消息队列中获取到的订单信息
                    MapRecord<String, Object, Object> voucherOrderRecord = read.get(0);
                    Map<Object, Object> voucherOrderRecordValue = voucherOrderRecord.getValue();
                    VoucherOrder voucherOrder = new VoucherOrder();
                    try {
                        voucherOrder = BeanUtil.fillBeanWithMap(voucherOrderRecordValue, voucherOrder, false);
                    } catch (Exception e) {
                        log.error("订单信息转换失败: {}", e.getMessage());
                    }
                    log.info("订单信息转换成功: {}", voucherOrder);

                    // 3.2 保存订单信息到数据库中
                    log.info("订单信息保存到数据库中: {}", voucherOrder);
                    handleVoucherOrder(voucherOrder);

                    // 4. 确认消息的完成 XACK stream消息队列名称 消费者组 消息ID
                    stringRedisTemplate.opsForStream().acknowledge(STREAM_KEY, CONSUMER_GROUP, voucherOrderRecord.getId());
                } catch (Exception e) {
                    log.error("订单处理异常!!!,消息队列中的消息未能成功处理,现在从pending-list中重新获取未处理的消息进行处理", e);
                    // 5. 从pending-list中重新获取未处理的消息进行处理
                    handleVoucherOrdeFromPendingList();
                }
            }
        }

        private void handleVoucherOrdeFromPendingList() {
            while (true) {
                try {
                    // 1. 获取stream.orders的pending-list中未成功处理的订单信息
                    // XREADGROUP GROUP g1 consumer1 COUNT 1 STREAMS stream.orders 0--不需要阻塞,获取失败直接返回null
                    /*List<MapRecord<String, Object, Object>> read = stringRedisTemplate.opsForStream().read(
                            Consumer.from(CONSUMER_GROUP, CONSUMER_NAME), // 指定消费者组和消费者
                            StreamReadOptions.empty().count(READ_COUNT), // 设置读取数量为1, 设置阻塞时间为2000ms
                            StreamOffset.create(STREAM_KEY, ReadOffset.from("0")) // 设置读取位置为pending-list的第一个消息
                    );*/
                    List<MapRecord<String, Object, Object>> read = null;
                    try {
                        read = stringRedisTemplate.opsForStream().read(
                                Consumer.from(CONSUMER_GROUP, CONSUMER_NAME),
                                StreamReadOptions.empty().count(READ_COUNT),
                                StreamOffset.create(STREAM_KEY, ReadOffset.from("0"))
                        );
                    } catch (Exception e) {
                        log.error("获取pending-list中未处理的订单消息失败,可能是pending-list不存在未处理的订单消息 {}", e.getMessage());
                    }

                    // 2. 判断订单信息是否为空
                    if (read == null || read.isEmpty()) {
                        // 消息获取失败, 继续循环获取, 直到获取到订单信息
                        log.error("消息获取未处理的订单消息失败, pending-list不存在未处理的订单消息");
                        break;//结束获取未处理的订单消息的循环
                    }

                    // 3. 订单消息获取成功, 取出订单信息, 保存订单信息到数据库中
                    // 3.1 解析从消息队列中获取到的订单信息
                    MapRecord<String, Object, Object> voucherOrderRecord = read.get(0);
                    Map<Object, Object> voucherOrderRecordValue = voucherOrderRecord.getValue();
                    VoucherOrder voucherOrder = new VoucherOrder();
                    try {
                        voucherOrder = BeanUtil.fillBeanWithMap(voucherOrderRecordValue, voucherOrder, false);
                    } catch (Exception e) {
                        log.error("订单信息转换失败: {}", e.getMessage());
                    }
                    log.info("订单信息转换成功: {}", voucherOrder);

                    // 3.2 保存订单信息到数据库中
                    log.info("订单信息保存到数据库中: {}", voucherOrder);
                    handleVoucherOrder(voucherOrder);

                    // 4. 确认消息的完成 XACK stream消息队列名称 消费者组 消息ID
                    stringRedisTemplate.opsForStream().acknowledge(STREAM_KEY, CONSUMER_GROUP, voucherOrderRecord.getId());
                } catch (Exception e) {
                    log.error("pending-list中未处理的订单消息处理异常!!!", e);
                    //休眠20ms继续从pending-list中获取未处理的订单消息
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                }
            }
        }
    }


    public void handleVoucherOrder(VoucherOrder newVoucherOrder) {
        /*创建redisson分布式锁对象*/
        RLock rLock = redissonClient.getLock("seckill_voucher_order_lock:" + newVoucherOrder.getUserId());
        /*尝试获取锁,如果获取锁成功,则返回true,否则返回false*/
        boolean isLock = false;
        try {
            isLock = rLock.tryLock(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        /*加锁成功*/
        if (!isLock) {
            // 获取锁失败，直接返回失败或者重试
            log.error("不允许重复下单！");
            return;
        }
        try {
            //注意：由于是spring的事务是放在threadLocal中，此时的是多线程，事务会失效,所以要使用代理对象(事务处理后),不能使用this
            proxy.createSecKillOrder(newVoucherOrder);
        } catch (Exception e) {
            log.error("订单处理异常!!!");
            throw new RuntimeException(e);
        } finally {
            rLock.unlock();//释放锁
        }
    }

    @Transactional(rollbackFor = Exception.class)/*使用数据库事务,保证这个方法的所有操作都同时成功或失败*/
    public void createSecKillOrder(VoucherOrder newVoucherOrder) {
        //4.判断用户是否已经购买过该代金卷,实现一人一单
        //根据用户id和代金卷id查询订单
        Integer count = this.query().eq("voucher_id", newVoucherOrder.getVoucherId()).eq("user_id", newVoucherOrder.getUserId()).count();
        if (count > 0) {
            log.error("该用户已经购买过该代金卷,不能重复购买!!!");
            return;
        }
        //5.扣减代金卷库存
        //seckillVoucherService.update().eq("voucher_id", voucherId).set("stock", seckillVoucher.getStock() - 1).update();
        boolean isSuccess = seckillVoucherService.update()
                .setSql("stock = stock - 1") // 扣减库存
                .eq("voucher_id", newVoucherOrder.getVoucherId()) // 确保操作的是指定的代金券
                .gt("stock", 0) // 乐观锁：库存大于0时才更新，避免并发问题
                .update();

        if (!isSuccess) {
            log.error("库存扣减失败，可能发生并发安全问题或库存不足");
            return;
        }

        //6.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setId(newVoucherOrder.getId());//订单id
        voucherOrder.setVoucherId(newVoucherOrder.getVoucherId());//代金卷id
        voucherOrder.setUserId(newVoucherOrder.getUserId());//用户id
        voucherOrder.setCreateTime(LocalDateTime.now());//创建时间
        voucherOrder.setUpdateTime(LocalDateTime.now());//更新时间
        voucherOrder.setStatus(VOUCHER_ORDER_STATUS_NOT_PAY);//订单状态,1:未支付

        log.info("创建订单:{}", voucherOrder);

        try {
            save(voucherOrder);//保存订单
        } catch (Exception e) {
            log.error("创建订单失败:{}", e.getMessage());
            throw new RuntimeException(e);
        }
    }


    /**
     * 秒杀抵用券活动
     * 主要就
     * 1.购买资格判断
     * 2.扣减库存下单
     *
     * @return
     */

/*    @Override
    public Result secKillVoucherActivity(Long voucherId) {
        *//*获取用户信息*//*
        UserDTO currentUser = (UserDTO) ThreadLocalUtil.get();
        Long currentUserId = currentUser.getId();
        //1.查询代金卷
        SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
        //1.1判断代金卷是否为空
        if (seckillVoucher == null) {
            log.error("秒杀代金卷不存在");
            return Result.fail("秒杀代金卷不存在");
        }
        //2.秒杀时间是否在有效期内
        LocalDateTime endTime = seckillVoucher.getEndTime();//结束时间
        LocalDateTime beginTime = seckillVoucher.getBeginTime();
        if (LocalDateTime.now().isAfter(endTime)) {
            log.error("秒杀抢购活动已结束");
            return Result.fail("秒杀抢购活动已结束");
        }
        if (LocalDateTime.now().isBefore(beginTime)) {
            log.error("秒杀抢购活动未开始");
            return Result.fail("秒杀抢购活动未开始");
        }
        //3.执行lua脚本,判断用户是否有购买资格,如果又则扣减库存,保存已经下单的用户信息,并返回代表成功下单的结果值0
        long generateOrderId = redisIdGenerate.generateId(SEC_KILL_VOUCHER_ORDER_KEY);
        //只有值参数,保存到lua的ARGV数组中,没有键参数,KEYS 数组中无值,传入空的集合
        Long execute = stringRedisTemplate.execute(
                DESKILL_VOUCHER_SCRIPT,
                Collections.emptyList(),//空KEYS
                voucherId.toString(),//代金卷id
                currentUserId.toString()//用户id
        );
        //4.判断是否成功下单
        //4.1 如果返回值不是0,则代表失败,返回错误信息
        if (execute != 0) {
            log.error("用户秒杀抢购失败,不具有购买资格,可能是库存不足,也可能是重复购买!!!");
            return Result.fail(execute == 1 ? "秒杀抢购失败,库存不足!!!" : "秒杀抢购失败,请不要重复购买!!!");
        }
        //todo 4.2 如果返回值是0,则代表成功,将秒杀优惠卷订单信息(优惠卷id,用户id,订单id)保存到redis阻塞队列中(后续异步写入数据库),并返回订单id
        //4.2.1封装秒杀优惠卷订单信息(优惠卷id,用户id,订单id)
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setVoucherId(voucherId);//优惠卷id
        voucherOrder.setUserId(currentUserId);//用户id
        voucherOrder.setId(generateOrderId);//订单id
        //4.2.2将秒杀优惠卷订单信息(优惠卷id,用户id,订单id)保存到阻塞队列中(后续异步线程写入数据库)
        orderTasks.add(voucherOrder);//将秒杀优惠卷订单信息(优惠卷id,用户id,订单id)保存到阻塞队列中(后续异步线程写入数据库)
        //3.获取代理对象
        proxy = (IVoucherOrderService) AopContext.currentProxy();
        //5.返回订单id
        return Result.ok(generateOrderId);
    }*/
    @Override
    public Result secKillVoucherActivity(Long voucherId) {
        /*获取用户信息*/
        UserDTO currentUser = (UserDTO) ThreadLocalUtil.get();
        Long currentUserId = currentUser.getId();
        //1.查询代金卷
        SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
        //1.1判断代金卷是否为空
        if (seckillVoucher == null) {
            log.error("秒杀代金卷不存在");
            return Result.fail("秒杀代金卷不存在");
        }
        //2.秒杀时间是否在有效期内
        LocalDateTime endTime = seckillVoucher.getEndTime();//结束时间
        LocalDateTime beginTime = seckillVoucher.getBeginTime();
        if (LocalDateTime.now().isAfter(endTime)) {
            log.error("秒杀抢购活动已结束");
            return Result.fail("秒杀抢购活动已结束");
        }
        if (LocalDateTime.now().isBefore(beginTime)) {
            log.error("秒杀抢购活动未开始");
            return Result.fail("秒杀抢购活动未开始");
        }
        //3.执行lua脚本,判断用户是否有购买资格,如果又则扣减库存,保存已经下单的用户信息,并返回代表成功下单的结果值0
        long generateOrderId = redisIdGenerate.generateId(SEC_KILL_VOUCHER_ORDER_KEY);
        //只有值参数,保存到lua的ARGV数组中,没有键参数,KEYS 数组中无值,传入空的集合
        Long execute = stringRedisTemplate.execute(
                DESKILL_VOUCHER_SCRIPT,
                Collections.emptyList(),//空KEYS
                voucherId.toString(),//代金卷id
                currentUserId.toString(),//用户id
                String.valueOf(generateOrderId)//订单id
                //String.valueOf(generateOrderId)//订单id
        );
        //4.判断是否成功下单
        //4.1 如果返回值不是0,则代表失败,返回错误信息
        if (execute != 0) {
            log.error("用户秒杀抢购失败,不具有购买资格,可能是库存不足,也可能是重复购买!!!");
            return Result.fail(execute == 1 ? "秒杀抢购失败,库存不足!!!" : "秒杀抢购失败,请不要重复购买!!!");
        }
        /*//todo 4.2 如果返回值是0,则代表成功,将秒杀优惠卷订单信息(优惠卷id,用户id,订单id)保存到redis阻塞队列中(后续异步写入数据库),并返回订单id
        //4.2.1封装秒杀优惠卷订单信息(优惠卷id,用户id,订单id)
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setVoucherId(voucherId);//优惠卷id
        voucherOrder.setUserId(currentUserId);//用户id
        voucherOrder.setId(generateOrderId);//订单id
        //4.2.2将秒杀优惠卷订单信息(优惠卷id,用户id,订单id)保存到阻塞队列中(后续异步线程写入数据库)
        orderTasks.add(voucherOrder);//将秒杀优惠卷订单信息(优惠卷id,用户id,订单id)保存到阻塞队列中(后续异步线程写入数据库)*/
        //3.获取代理对象
        proxy = (IVoucherOrderService) AopContext.currentProxy();
        //5.返回订单id
        return Result.ok(generateOrderId);
    }
/*    @Override
    public Result secKillVoucherActivity(Long voucherId) {
        *//*获取用户信息*//*
        UserDTO currentUser = (UserDTO) ThreadLocalUtil.get();
        Long currentUserId = currentUser.getId();
        //1.查询代金卷
        SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
        //1.1判断代金卷是否为空
        if (seckillVoucher == null) {
            log.error("秒杀代金卷不存在");
            return Result.fail("秒杀代金卷不存在");
        }
        //2.秒杀时间是否在有效期内
        LocalDateTime endTime = seckillVoucher.getEndTime();//结束时间
        LocalDateTime beginTime = seckillVoucher.getBeginTime();
        if (LocalDateTime.now().isAfter(endTime)) {
            log.error("秒杀抢购活动已结束");
            return Result.fail("秒杀抢购活动已结束");
        }
        if (LocalDateTime.now().isBefore(beginTime)) {
            log.error("秒杀抢购活动未开始");
            return Result.fail("秒杀抢购活动未开始");
        }
        //3.秒杀代金卷的库存是否足够
        if (seckillVoucher.getStock() < 1) {
            log.error("秒杀代金卷已经被抢光");
            return Result.fail("秒杀代金卷已经被抢光");
        }

        //尝试获取分布式锁
        *//*锁的key名要拼接用户id,防止锁的范围过大,只锁用户id相同请求线程*//*
        //ILockRedisImpUtil lock = new ILockRedisImpUtil(stringRedisTemplate, "sec_kill_voucher_order:" + currentUserId.toString());
        //boolean tryLockSuccess = lock.tryLock(8);
        RLock rLock = redissonClient.getLock("sec_kill_voucher_order:" + currentUserId.toString());
        if (rLock == null) {
            log.error("获取分布式锁失败");
            return Result.fail("秒杀抢购失败,请稍后重试,系统繁忙");
        }
        boolean tryLockSuccess = false;//默认获取锁失败
        try {
            tryLockSuccess = rLock.tryLock(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            log.error("获取分布式锁失败");
            throw new RuntimeException(e);
        }
        if (!tryLockSuccess) {
            log.error("获取分布式锁失败");
            return Result.fail("秒杀抢购失败,请稍后重试,请注意不要重复点击购买按钮");
        }
        try {
            VoucherOrderServiceImpl proxy = (VoucherOrderServiceImpl) AopContext.currentProxy();
            Result secKillVoucherOrder = proxy.createSecKillVoucherOrder(voucherId, currentUserId);
            return secKillVoucherOrder;
        } finally {
            //释放锁
            rLock.unlock();
        }

        *//*这段代码的主要功能是在用户尝试购买秒杀代金券时，
        通过synchronized关键字实现对当前用户的购买操作进行同步控制，
        确保在多线程环境下，不同线程对同一用户的购买请求能够安全地顺序执行，
        从而防止同一用户重复购买代金券的情况。
        同时，它通过获取AOP代理对象来确保事务正确处理，增强了代码的可维护性和健壮性。
        如果不使用proxy而是直接使用this,可能导致事务失效的问题,导致事务不生效,导致数据不一致
        *//*
    }*/

    /**
     * syncronized关键字实现分布式锁,解决并发安全问题,1.锁为当前对象<br>
     * 2.同一时间只有一个线程可以执行该方法,其他线程只能等待,直到该线程执行完毕,其他线程才能继续执行<br>
     * 该方法的作用是确保同一时间只有一个线程可以执行该方法,其他线程只能等待,直到该线程执行完毕,其他线程才能继续执行<br>
     * 该方法的返回值和参数类型必须与原方法一致<br>
     * 秒杀抵用券活动订单创建
     *
     * @param voucherId
     * @param currentUserId
     * @return
     */

    @Transactional(rollbackFor = Exception.class)/*使用数据库事务,保证这个方法的所有操作都同时成功或失败*/
    public Result createSecKillVoucherOrder(Long voucherId, Long currentUserId) {
        //4.判断用户是否已经购买过该代金卷,实现一人一单
        //根据用户id和代金卷id查询订单
        Integer count = this.query().eq("voucher_id", voucherId).eq("user_id", currentUserId).count();
        if (count > 0) {
            log.error("该用户已经购买过该代金卷,不能重复购买!!!");
            return Result.fail("您已经购买过该代金卷,不能重复购买!!!");
        }
        //5.扣减代金卷库存
        //seckillVoucherService.update().eq("voucher_id", voucherId).set("stock", seckillVoucher.getStock() - 1).update();
        boolean isSuccess = seckillVoucherService.update()
                .setSql("stock = stock - 1") // 扣减库存
                .eq("voucher_id", voucherId) // 确保操作的是指定的代金券
                .gt("stock", 0) // 乐观锁：库存大于0时才更新，避免并发问题
                .update();

        if (!isSuccess) {
            log.error("库存扣减失败，可能发生并发安全问题或库存不足");
            return Result.fail("库存扣减失败，请稍后重试");
        }
        /*set stock = stock - 1 version=version + 1 where voucher_id = #{voucherId} and version = #{version}*/
        /*但是数据库表结构已定,所以无法使用版本号法*/
        /*set stock = stock - 1 where voucher_id = #{voucherId} and stock = 查询出来的库存*/
        /*确保查询出来的版本号和更新数据库时的版本号一致,才算更新成功---乐观锁*/
        /*if (!isSuccess) {
            log.error("库存扣减失败,可能发生并发安全问题");
            return Result.fail("库存扣减失败,可能发生并发安全问题");
        }*/

        //6.创建订单
        long orderId = redisIdGenerate.generateId(VOUCHER_ORDER_KEY_PREFIX);
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setId(orderId);//订单id
        voucherOrder.setVoucherId(voucherId);//代金卷id
        UserDTO userDTO = (UserDTO) ThreadLocalUtil.get();
        Long userId = userDTO.getId();
        voucherOrder.setUserId(userId);//用户id
        voucherOrder.setCreateTime(LocalDateTime.now());//创建时间
        voucherOrder.setUpdateTime(LocalDateTime.now());//更新时间
        voucherOrder.setStatus(VOUCHER_ORDER_STATUS_NOT_PAY);//订单状态,1:未支付

        log.info("创建订单:{}", voucherOrder);

        save(voucherOrder);//保存订单

        return Result.ok(orderId);

    }
    /*    @Transactional(rollbackFor = Exception.class)*//*使用数据库事务,保证这个方法的所有操作都同时成功或失败*//*
    public Result createSecKillVoucherOrder(Long voucherId, Long currentUserId) {
        //4.判断用户是否已经购买过该代金卷,实现一人一单
        //根据用户id和代金卷id查询订单
        *//*intern()方法:该方法的作用是确保锁的唯一性,原因是:intern() 方法会从字符串常量池中查找与当前字符串内容相同的字符串*//*
     *//*锁为用户的id,锁只对相同id的用户失效,不同id的用户的锁不同,互不影响--防止同一个用户购买多张秒杀代金卷*//*
        synchronized (currentUserId.toString().intern()) {//如果锁是id=5,则有一个用户id为5的线程在执行该方法,其他用户id为5的线程只能等待
            Integer count = this.query().eq("voucher_id", voucherId).eq("user_id", currentUserId).count();
            if (count > 0) {
                log.error("该用户已经购买过该代金卷,不能重复购买!!!");
                return Result.fail("您已经购买过该代金卷,不能重复购买!!!");
            }
            //5.扣减代金卷库存
            //seckillVoucherService.update().eq("voucher_id", voucherId).set("stock", seckillVoucher.getStock() - 1).update();
            boolean isSuccess = seckillVoucherService.update()
                    .setSql("stock = stock - 1") // 扣减库存
                    .eq("voucher_id", voucherId) // 确保操作的是指定的代金券
                    .gt("stock", 0) // 乐观锁：库存大于0时才更新，避免并发问题
                    .update();

            if (!isSuccess) {
                log.error("库存扣减失败，可能发生并发安全问题或库存不足");
                return Result.fail("库存扣减失败，请稍后重试");
            }
            *//*set stock = stock - 1 version=version + 1 where voucher_id = #{voucherId} and version = #{version}*//*
     *//*但是数据库表结构已定,所以无法使用版本号法*//*
     *//*set stock = stock - 1 where voucher_id = #{voucherId} and stock = 查询出来的库存*//*
     *//*确保查询出来的版本号和更新数据库时的版本号一致,才算更新成功---乐观锁*//*
     *//*if (!isSuccess) {
            log.error("库存扣减失败,可能发生并发安全问题");
            return Result.fail("库存扣减失败,可能发生并发安全问题");
        }*//*

            //6.创建订单
            long orderId = redisIdGenerate.generateId(VOUCHER_ORDER_KEY_PREFIX);
            VoucherOrder voucherOrder = new VoucherOrder();
            voucherOrder.setId(orderId);//订单id
            voucherOrder.setVoucherId(voucherId);//代金卷id
            UserDTO userDTO = (UserDTO) ThreadLocalUtil.get();
            Long userId = userDTO.getId();
            voucherOrder.setUserId(userId);//用户id
            voucherOrder.setCreateTime(LocalDateTime.now());//创建时间
            voucherOrder.setUpdateTime(LocalDateTime.now());//更新时间
            voucherOrder.setStatus(VOUCHER_ORDER_STATUS_NOT_PAY);//订单状态,1:未支付

            log.info("创建订单:{}", voucherOrder);

            save(voucherOrder);//保存订单

            return Result.ok(orderId);
        }
    }*/

/*    private synchronized Result createSecKillVoucherOrder(Long voucherId, Long currentUserId) {
        //4.判断用户是否已经购买过该代金卷,实现一人一单
        //根据用户id和代金卷id查询订单
        Integer count = this.query().eq("voucher_id", voucherId).eq("user_id", currentUserId).count();
        if (count > 0) {
            log.error("该用户已经购买过该代金卷,不能重复购买!!!");
            return Result.fail("您已经购买过该代金卷,不能重复购买!!!");
        }
        //5.扣减代金卷库存
        //seckillVoucherService.update().eq("voucher_id", voucherId).set("stock", seckillVoucher.getStock() - 1).update();
        boolean isSuccess = seckillVoucherService.update()
                .setSql("stock = stock - 1") // 扣减库存
                .eq("voucher_id", voucherId) // 确保操作的是指定的代金券
                .gt("stock", 0) // 乐观锁：库存大于0时才更新，避免并发问题
                .update();

        if (!isSuccess) {
            log.error("库存扣减失败，可能发生并发安全问题或库存不足");
            return Result.fail("库存扣减失败，请稍后重试");
        }
        *//*set stock = stock - 1 version=version + 1 where voucher_id = #{voucherId} and version = #{version}*//*
     *//*但是数据库表结构已定,所以无法使用版本号法*//*
     *//*set stock = stock - 1 where voucher_id = #{voucherId} and stock = 查询出来的库存*//*
     *//*确保查询出来的版本号和更新数据库时的版本号一致,才算更新成功---乐观锁*//*
     *//*if (!isSuccess) {
            log.error("库存扣减失败,可能发生并发安全问题");
            return Result.fail("库存扣减失败,可能发生并发安全问题");
        }*//*

        //6.创建订单
        long orderId = redisIdGenerate.generateId(VOUCHER_ORDER_KEY_PREFIX);
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setId(orderId);//订单id
        voucherOrder.setVoucherId(voucherId);//代金卷id
        UserDTO userDTO = (UserDTO) ThreadLocalUtil.get();
        Long userId = userDTO.getId();
        voucherOrder.setUserId(userId);//用户id
        voucherOrder.setCreateTime(LocalDateTime.now());//创建时间
        voucherOrder.setUpdateTime(LocalDateTime.now());//更新时间
        voucherOrder.setStatus(VOUCHER_ORDER_STATUS_NOT_PAY);//订单状态,1:未支付

        log.info("创建订单:{}", voucherOrder);

        save(voucherOrder);//保存订单
        return Result.ok(orderId);
    }*/


}
